/* main.c - Application main entry point */

/*
 * Copyright (c) 2022 Xiaomi Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/types.h>
#include <stddef.h>
#include <string.h>
#include <errno.h>
#include <sys/printk.h>
#include <sys/reboot.h>
#include <sys/byteorder.h>
#include <zephyr.h>

#include <nuttx/fs/fs.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/fs/ioctl.h>

#include "mible_api.h"

static struct mtd_geometry_s geo;
static struct mtd_dev_s *mtd_dev_otp;
static struct mtd_dev_s *mtd_ota_banks[2];

mible_status_t mible_nvm_init(void)
{
	int ret;
	struct inode *node;

	ret = find_mtddriver("/dev/ota1", &node);
	if (ret) {
		return MI_ERR_INTERNAL;
	}

	ret = node->u.i_mtd->ioctl(node->u.i_mtd,
				   MTDIOC_GEOMETRY, (unsigned long)&geo);
	if (ret) {
		return MI_ERR_INTERNAL;
	}

	if (!geo.erasesize || !geo.neraseblocks) {
		return MI_ERR_INTERNAL;
	}

	mtd_ota_banks[0] = node->u.i_mtd;

	ret = find_mtddriver("/dev/ota2", &node);
	if (ret) {
		return MI_ERR_INTERNAL;
	}

	mtd_ota_banks[1] = node->u.i_mtd;

	return MI_SUCCESS;
}

static struct mtd_dev_s *mtd_dev_get(uint32_t *address)
{
	uint32_t len = geo.neraseblocks * geo.erasesize;

	if (*address < len) {
		return mtd_ota_banks[0];
	} else {
		*address -= len;
		return mtd_ota_banks[1];
	}
}

mible_status_t mible_nvm_read(void * p_data, uint32_t length, uint32_t address)
{
	struct mtd_dev_s *mtd_dev = mtd_dev_get(&address);

	if (!mtd_dev) {
		return MI_ERR_INTERNAL;
	}

	(void)mtd_dev->read(mtd_dev, address, length, p_data);

	return MI_SUCCESS;
}

mible_status_t mible_nvm_write(void * p_data, uint32_t length, uint32_t address)
{
	struct mtd_dev_s *mtd_dev = mtd_dev_get(&address);

	if (!mtd_dev) {
		return MI_ERR_INTERNAL;
	}

	if ((address % geo.erasesize) == 0) {
		(void)mtd_dev->erase(mtd_dev, address / geo.erasesize, 1);
	}

	(void)mtd_dev->write(mtd_dev, address, length, p_data);

	return MI_SUCCESS;
}

mible_status_t mible_upgrade_firmware(void)
{
	uint8_t src[256], dst[256];
	struct mtd_dev_s *header;
	struct mtd_dev_s *boot;
	size_t len, addr = 0;
	struct inode *node;
	int ret;

	ret = find_mtddriver("/dev/boot", &node);
	if (ret) {
		return MI_ERR_INTERNAL;
	}

	boot = node->u.i_mtd;

	ret = find_mtddriver("/dev/setting", &node);
	if (ret) {
		return MI_ERR_INTERNAL;
	}

	header = node->u.i_mtd;

	(void)boot->erase(boot, 0, 1);

	while (addr < geo.erasesize) {
		len = ARRAY_SIZE(src);

		(void)header->read(header, addr, len, src);

		(void)boot->write(boot, addr, len, src);

		/* Readback & Verify */
		(void)boot->read(boot, addr, len, dst);
		if (memcmp(src, dst, len)) {
			return MI_ERR_INTERNAL;
		}

		addr += len;
	}

	src[0] = 0x4b;
	(void)boot->write(boot, 8, 1, &src[0]);

	mible_reboot();

	return MI_SUCCESS;
}

mible_status_t mible_otp_init(void)
{
	int ret;
	struct inode *node;

	ret = find_mtddriver("/dev/cert", &node);
	if (ret) {
		return MI_ERR_INTERNAL;
	}

	mtd_dev_otp = node->u.i_mtd;

	return MI_SUCCESS;
}

mible_status_t mible_otp_read(void * p_data, uint32_t length, uint32_t address)
{
	if (!mtd_dev_otp) {
		return MI_ERR_INTERNAL;
	}

	(void)mtd_dev_otp->read(mtd_dev_otp, address, length, p_data);

	return MI_SUCCESS;
}
